/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.jesseenglish.swingftfy.extensions;

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.LinkedList;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JTabbedPane;

/**
 *
 * @author english1
 */
public abstract class FTabbedPane extends JTabbedPane implements FocusListener, HierarchyListener, MouseListener, MouseMotionListener {

    private static ImageIcon CLOSE_TAB_ICON = new ImageIcon(FTabbedPane.class.getResource("/images/closetab.gif"));
    private static ImageIcon CLOSE_TAB_OVER_ICON = new ImageIcon(FTabbedPane.class.getResource("/images/closetabover.gif"));

    public static LinkedList<FTabbedPane> REGISTERED_PANES = new LinkedList();

    private int draggedTab;
    private JDialog dragger;

    public FTabbedPane() {
        this.draggedTab = -1;
        this.dragger = null;

        FTabbedPane.REGISTERED_PANES.add(this);

        this.addMouseListener(this);
        this.addMouseMotionListener(this);
        this.addHierarchyListener(this);
        this.addFocusListener(this);
    }

    public abstract void openTabComponentInNewWindow(Component c, MouseEvent evt);
    public abstract boolean isTabComponentDraggable(Component c, int tab);
    public abstract boolean isTabComponentClosable(Component c, int tab);
    public abstract boolean isTabComponentDroppable(Component c);

    public void focusGained(FocusEvent evt) {
        FTabbedPane.REGISTERED_PANES.remove(this);
        FTabbedPane.REGISTERED_PANES.add(0, this);
    }
    public void focusLost(FocusEvent evt) {}

    public void hierarchyChanged(HierarchyEvent evt) {
        try {
            this.getLocationOnScreen();
            if (!FTabbedPane.REGISTERED_PANES.contains(this))
                FTabbedPane.REGISTERED_PANES.add(this);
        } catch (Exception err) {
            //OFF SCREEN, REMOVE FROM GLOBAL LIST
            FTabbedPane.REGISTERED_PANES.remove(this);
        }
    }

    public void mouseEntered(MouseEvent evt) {}
    public void mouseExited(MouseEvent evt) {}
    public void mousePressed(MouseEvent evt) {
        this.draggedTab = this.getUI().tabForCoordinate(this, evt.getX(), evt.getY());

        if (draggedTab == -1)
            return;
        
        String title = this.getTitleAt(this.draggedTab);

        if (!isTabComponentDraggable(this.getComponentAt(this.draggedTab), this.draggedTab))
            return;
        
        JLabel draggerTitle = new JLabel(title);

        int width = this.getGraphics().getFontMetrics().stringWidth(title) + 4;
        int height = this.getGraphics().getFontMetrics().getHeight() + 2;

        Point location = new Point();
        location.x = evt.getXOnScreen() - (width / 2);
        location.y = evt.getYOnScreen() - height;

        this.dragger = new JDialog();
        this.dragger.setUndecorated(true);
        this.dragger.setLayout(new GridBagLayout());
        this.dragger.setSize(width, height);        
        this.dragger.add(draggerTitle);
        this.dragger.setLocation(location);
    }
    public void mouseReleased(MouseEvent evt) {
        if (this.dragger == null)
            return;
        if (this.draggedTab == -1)
            return;

        if ((evt.getX() < 0) ||
            (evt.getX() > this.getWidth()) ||
            (evt.getY() < 0) ||
            (evt.getY() > this.getHeight())) {

            Component c = this.getComponentAt(this.draggedTab);
            String title = this.getTitleAt(this.draggedTab);

            FTabbedPane underDragger = getOtherAtLocation(evt.getLocationOnScreen());
            if (underDragger != null) {
                if (underDragger.isTabComponentDroppable(c)) {
                    underDragger.addTab(title, c);
                    underDragger.suggestDroppable(null);
                }
            }
            else {
                openTabComponentInNewWindow(c, evt);
                this.removeTabAt(this.draggedTab);
            }            
        }
        else {
            this.requestFocus();
            this.requestFocusInWindow();
            this.getSelectedComponent().requestFocus();
            this.getSelectedComponent().requestFocusInWindow();
        }

        this.draggedTab = -1;
        this.dragger.setVisible(false);
        this.dragger = null;
    }
    public void mouseClicked(MouseEvent evt) {}

    public void mouseDragged(MouseEvent evt){
        if (this.dragger == null)
            return;

        this.dragger.setVisible(true);

        Point location = new Point();
        location.x = evt.getXOnScreen() - (this.dragger.getWidth() / 2);
        location.y = evt.getYOnScreen() - this.dragger.getHeight();

        this.dragger.setLocation(location);

        FTabbedPane underDragger = getOtherAtLocation(evt.getLocationOnScreen());
        if (underDragger != null && underDragger != this) {
            underDragger.suggestDroppable(this.getComponentAt(this.draggedTab));
        }
        for (int i = 0; i < FTabbedPane.REGISTERED_PANES.size(); i++) {
            FTabbedPane curPane = FTabbedPane.REGISTERED_PANES.get(i);
            if (curPane != underDragger)
                curPane.suggestDroppable(null);
        }
    }

    public void mouseMoved(MouseEvent evt) {}

    protected FTabbedPane getOtherAtLocation(Point p) {
        
        for (int i = 0; i < FTabbedPane.REGISTERED_PANES.size(); i++) {
            FTabbedPane other = FTabbedPane.REGISTERED_PANES.get(i);
            if ((other.getLocationOnScreen().x <= p.x) &&
                (other.getLocationOnScreen().x + other.getWidth() >= p.x) &&
                (other.getLocationOnScreen().y <= p.y) &&
                (other.getLocationOnScreen().y + other.getHeight() >= p.y)) {

                return other;
            }
        }

        return null;
    }

    protected void suggestDroppable(Component c) {
        if (c == null) {
            this.setBackground(Color.WHITE);
        }
        else if (isTabComponentDroppable(c)) {
            this.setBackground(Color.GREEN);
        }
        else {
            this.setBackground(Color.RED);
        }
    }


    @Override
    public void addTab(String name, Component c) {
        super.addTab(name, c);
        if (isTabComponentClosable(c, this.getTabCount() - 1))
            this.setIconAt(this.getTabCount() - 1, new TabCloseIcon(FTabbedPane.CLOSE_TAB_ICON, FTabbedPane.CLOSE_TAB_OVER_ICON));
        this.setSelectedIndex(this.getTabCount() - 1);
    }

    private class TabCloseIcon implements Icon {

        private final Icon mIcon;
        private final Icon mOverIcon;
        private JTabbedPane mTabbedPane = null;
        private transient Rectangle mPosition = null;
        private Point mousePoint = new Point(-1, -1);

        /**
         * Creates a new instance of TabCloseIcon.
         */
        public TabCloseIcon(Icon icon, Icon overIcon) {
            mIcon = icon;
            mOverIcon = overIcon;
        }

        /**
         * when painting, remember last position painted.
         */
        public void paintIcon(Component c, Graphics g, int x, int y) {

            if (null == mTabbedPane) {
                mTabbedPane = (JTabbedPane) c;
                mTabbedPane.addMouseListener(new MouseAdapter() {
                    public void mouseReleased(MouseEvent evt) {
                        // asking for isConsumed is *very* important, otherwise more than one tab might get closed!
                        if (!evt.isConsumed() && mPosition.contains(evt.getX(), evt.getY())) {
                            final int index = mTabbedPane.getSelectedIndex();
                            if (isTabComponentClosable(mTabbedPane.getTabComponentAt(index), index)) {
                                mTabbedPane.remove(index);
                            }
                            evt.consume();
                        }
                    }
                });

                mTabbedPane.addMouseMotionListener(new MouseAdapter() {
                    public void mouseMoved(MouseEvent evt) {
                        mousePoint = evt.getPoint();
                        evt.consume();
                        validate();
                        repaint();
                    }
                });
            }

            mPosition = new Rectangle(x, y, getIconWidth(), getIconHeight());

            if (mPosition.contains(mousePoint)) {
                mOverIcon.paintIcon(c, g, x, y);
            }
            else {
                mIcon.paintIcon(c, g, x, y);
            }
        }

        /**
         * just delegate
         */
        public int getIconWidth() {
            return mIcon.getIconWidth();
        }

        /**
         * just delegate
         */
        public int getIconHeight() {
            return mIcon.getIconHeight();
        }
    }
}
